﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using SharedLib;
using SharedLib.MessageFramework.Messages;
using SharedLib.MessageFramework.Handlers;

namespace SharedLib.MessageFramework
{
    public partial class MessageDispatcher
    {
        //private ManualResetEvent _loopResetEvent = new ManualResetEvent(false);
        //private ManualResetEvent _enQueueResetEvent = new ManualResetEvent(true);
        private static Object ht_lock = new Object();//handler thread lock

        private delegate void ProcessMessageDelegate(IAppMessage msg);

        public const int LOOP_INTERVAL = 100;

        /// <summary>
        /// Default handler thead capacity, single thread for each message type
        /// </summary>
        public const int DEFAULT_THREAD_CAPACITY = 1;
        /// <summary>
        /// Capacity of the threads available for each handler
        /// </summary>
        private HandlerMonitorList _threadCapacity = new HandlerMonitorList();

        protected virtual void OnMessageProcessing(IAppMessage msg) { }
        protected virtual void OnMessageProcessed(IAppMessage msg) { }
        public event EventHandler<ExceptionEventArgs> OnException;
        public MessageDispatcher()
        {
            threadMessageQueue = new Thread(new ThreadStart(CheckMessageQueue));
            threadMessageQueue.Start();
        }

        #region public methods
        
        /// <summary>
        /// Set the max thread count of the given handler
        /// </summary>
        /// <param name="typeHandlerUID">The UID of the handler</param>
        /// <param name="capacity">Max thread count</param>
        public void SetHandlerCapacity(string typeHandlerUID, int capacity)
        {
            if (String.IsNullOrEmpty(typeHandlerUID)) throw new ArgumentNullException("typeHandlerUID");
            if (capacity <= 0) throw new ArgumentOutOfRangeException("capacity", capacity, "capacity must > 0");

            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, capacity, 0));
                }
                else
                {
                    _threadCapacity[typeHandlerUID].MaxCapacity = capacity;
                }
            }
        }

        /// <summary>
        /// Enqueue message to proper queue according to its priority
        /// </summary>
        /// <param name="msg"></param>
        public virtual void EnqueueMessage(IAppMessage msg)
        {
            if (msg == null) throw new ArgumentNullException("msg");
            if (msg.Handler == null) throw new ArgumentException("Message Handler could not be null.", "msg");
            if (String.IsNullOrEmpty(msg.HandlerUID)) throw new ArgumentException("Message Handler UID could not be empty.", "msg");

            if (msg.Priority == MessagePriority.High)
            {
                _messageQueueHighPriority.Enqueue(msg);
            }
            else
            {
                _messageQueueMediumPriority.Enqueue(msg);
            }
        }

        /// <summary>
        /// Stop the message processing loop
        /// </summary>
        public void Stop()
        {
            Stop(_messageQueueMediumPriority);
            Stop(_messageQueueHighPriority);
        }
        #endregion

        /// <summary>
        /// Complete all queued tasks before exit
        /// </summary>
        protected virtual bool ExitUntilQueueCleared
        {
            get { return true; }
        }

        private bool CheckOutHandler(string typeHandlerUID)
        {
            bool rtn = false;
            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, DEFAULT_THREAD_CAPACITY, 1));
                    rtn = true;
                }
                else
                {
                    if (_threadCapacity[typeHandlerUID].HasCapacity())
                    {
                        _threadCapacity[typeHandlerUID].CurrentUsage++;
                        rtn = true;
                    }
                }
            }
            return rtn;
        }

        private void CheckInHandler(string typeHandlerUID)
        {
            lock (ht_lock)
            {
                if (!_threadCapacity.Contains(typeHandlerUID))
                {
                    _threadCapacity.Add(new HandlerMonitor(typeHandlerUID, DEFAULT_THREAD_CAPACITY, 0));
                }
                else
                {
                    _threadCapacity[typeHandlerUID].CurrentUsage--;
                }
            }
        }

        private long _killThreadFlag = 0;
        /// <summary>
        /// Thread-safe on-off switch flag
        /// </summary>
        protected bool KillThreadFlag
        {
            get { 
                return (Interlocked.Read(ref _killThreadFlag) == 1); 
            }
            set {
                if (value)
                {
                    Interlocked.Exchange(ref _killThreadFlag, 1);
                }
                else
                {
                    Interlocked.Exchange(ref _killThreadFlag, 0);
                }
            }
        }
        /// <summary>
        /// Queue thread
        /// </summary>
        protected Thread threadMessageQueue;
        private ConcurrentQueue<IAppMessage> _messageQueueMediumPriority = new ConcurrentQueue<IAppMessage>(); //Message queue for medium priority tasks
        private ConcurrentQueue<IAppMessage> _messageQueueHighPriority = new ConcurrentQueue<IAppMessage>(); //Message queue for high priority tasks    

        protected bool PrepareHandler(IAppMessage msg)
        {
            if (msg != null && msg.Handler != null)
            {
                //Console.WriteLine("[" + System.DateTime.Now.ToString("HH:mm:ss fff") + "] Prepare Handler: " + msg.HandlerUID);
                if (CheckOutHandler(msg.HandlerUID))
                {
                    //Console.WriteLine("[" + System.DateTime.Now.ToString("HH:mm:ss fff") + "] Handler Checked Out: " + msg.HandlerUID);
                    return true;
                }
                else
                {
                    //Console.WriteLine("[" + System.DateTime.Now.ToString("HH:mm:ss fff") + "] Failed to Check Out Handler: " + msg.HandlerUID);
                    EnqueueMessage(msg);
                }
            }
            //TODO: Exception
            return false;
        }

        protected virtual void PerformCallback(IAppMessage msg)
        {
            if (msg != null)
            {
                if (msg.CallBack != null)
                {

                    if (msg.Sender is Control)
                    {
                        Control senderControl = msg.Sender as Control;
                        if (senderControl.InvokeRequired)
                        {
                            senderControl.Invoke(msg.CallBack, msg);//Invoke on UI thread (future: let UI itself to handle this)
                        }
                    }
                    else
                    {
                        msg.CallBack.Invoke(msg);
                    }
                }
            }
        }

        protected virtual void ProcessMessage(IAppMessage msg)
        {
            if (msg != null && msg.Handler != null)
            {
                Exception messageException = null;
                Exception callbackException = null;
                try
                {
                    object tmpInstance = Activator.CreateInstance(msg.Handler);
                    if (tmpInstance != null && tmpInstance is IAppMessageHandler)
                    {
                        IAppMessageHandler handler = (IAppMessageHandler)tmpInstance;

                        Console.WriteLine("[" + System.DateTime.Now.ToString("HH:mm:ss fff") + "] Begin Process Message: " + msg.HandlerUID);

                        OnMessageProcessing(msg);
                        try
                        {
                            handler.ProcessMessage(msg);
                        }
                        catch (Exception ex)
                        {
                            msg.Exception = new MessageException(msg, "Error processing message \"" + msg.HandlerUID + "\".", ex);
                            messageException = msg.Exception;
                        }
                        try
                        {
                            PerformCallback(msg); //Note: Callback should be responsible to handle msg.Exception if exists
                        }
                        catch (Exception ex)
                        {
                            msg.Exception = new MessageException(msg, "Error performing callback for message \"" + msg.HandlerUID + "\".", ex);
                            callbackException = msg.Exception;
                        }
                        OnMessageProcessed(msg);
                    }
                    else
                    {
                        msg.Exception = new MessageException(msg, "Failed to create Handler instance for message \"" + msg.HandlerUID + "\".");
                    }
                }
                catch (Exception ex)
                {
                    msg.Exception = new MessageException(msg, "Unexcepted error occurred when processing message \"" + msg.HandlerUID + "\".", ex);
                }
                finally
                {
                    CheckInHandler(msg.HandlerUID);
                }

                if (messageException != null) 
                    OnException.InvokeEvent(this, new ExceptionEventArgs(messageException));
                if (callbackException != null && callbackException != messageException) 
                    OnException.InvokeEvent(this, new ExceptionEventArgs(callbackException));
                if (msg.Exception != null && msg.Exception != messageException && msg.Exception != callbackException) 
                    OnException.InvokeEvent(this, new ExceptionEventArgs(msg.Exception));
            }
        }

        private ConcurrentQueue<IAppMessage> GetNonEmptyQueueOfHighestPriority()
        {
            if (_messageQueueHighPriority.Count > 0) return _messageQueueHighPriority;
            else if (_messageQueueMediumPriority.Count > 0) return _messageQueueMediumPriority;
            else return null;
        }

        /// <summary>
        /// (Standalone Thread) Check Message Queue and dispatch messages
        /// </summary>
        protected virtual void CheckMessageQueue()
        {
            IAppMessage msg;
            while (!KillThreadFlag)
            {
                while (ExitUntilQueueCleared || !KillThreadFlag)
                {
                    ConcurrentQueue<IAppMessage> queue = GetNonEmptyQueueOfHighestPriority();
                    if (queue == null || queue.Count == 0) break;
                    if (queue.TryDequeue(out msg))
                    {
                        if (msg != null)
                        {
                            if (PrepareHandler(msg))//handler thread capacity checking
                            {
                                ProcessMessageDelegate asyncDelegate = new ProcessMessageDelegate(this.ProcessMessage);
                                asyncDelegate.BeginInvoke(msg, null, null);
                            }
                        }
                    }
                }
                Thread.Sleep(LOOP_INTERVAL);
                //_enQueueResetEvent.Reset();//Block incoming message until _loopResetEvent.Reset() is completed
                //ConcurrentQueue<IAppMessage> q = GetNonEmptyQueueOfHighestPriority();
                //if (q == null || q.Count == 0)//If queue is not empty, continue process
                //{
                //    _loopResetEvent.Reset();
                //    _enQueueResetEvent.Set();
                //    //Console.WriteLine("==========Set Enqueue");
                //    //Console.WriteLine("==========Wait Loop");
                //    _loopResetEvent.WaitOne();
                //}
            }
        }

        private void Stop(ConcurrentQueue<IAppMessage> queue)
        {
            KillThreadFlag = true;
            IAppMessage msg;
            //_loopResetEvent.Set();
            while (queue.Count > 0)
            {
                if (queue.TryDequeue(out msg))
                {
                    msg.CancelFlag = true;
                }
            }
        }


    }
}
